#include "MultiTextureShader.h"

#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>

#include "Texture.h"

using namespace Graphics;
RTTI_DEFINITIONS(MultiTextureShader)

MultiTextureShader::MultiTextureShader(void)
	:Shader("MultiTextureShader","")
{
}

MultiTextureShader::~MultiTextureShader(void)
{
}

bool MultiTextureShader::Initialize(HWND hwnd)
{
	// Initialize the vertex and pixel shaders.
	bool result = InitializeShader(hwnd, "..\\..\\assets\\shaders\\multitexture.vs", "..\\..\\assets\\shaders\\multitexture.ps");
	
	return result;
}

void MultiTextureShader::Denitialize()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	DeinitializeShader();

	return;
}

bool MultiTextureShader::Render(int indexCount, D3DXMATRIX worldMatrix, 
				     D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, TextureArray* textureArray)
{
	Graphics::D3DDevice* device = GetPlatform()->GetDevice()->As<Graphics::D3DDevice>();

	// Set the shader parameters that it will use for rendering.
	bool result = SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, textureArray);

	// Now render the prepared buffers with the shader.
	result = RenderShader(device, indexCount);

	return result;
}

bool MultiTextureShader::InitializeShader(HWND hwnd, const char* vsFilename, const char* psFilename)
{
	Graphics::D3DDevice* device = GetPlatform()->GetDevice()->As<Graphics::D3DDevice>();
	if(Shader::InitializeShader(device, hwnd, vsFilename, psFilename))
	{
		// Now setup the layout of the data that goes into the shader.
		// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
		D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
		// Create the vertex input layout description.
		// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
		polygonLayout[0].SemanticName = "POSITION";
		polygonLayout[0].SemanticIndex = 0;
		polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
		polygonLayout[0].InputSlot = 0;
		polygonLayout[0].AlignedByteOffset = 0;
		polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
		polygonLayout[0].InstanceDataStepRate = 0;

		polygonLayout[1].SemanticName = "COLOR";
		polygonLayout[1].SemanticIndex = 0;
		polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
		polygonLayout[1].InputSlot = 0;
		polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
		polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
		polygonLayout[1].InstanceDataStepRate = 0;

		polygonLayout[2].SemanticName = "TEXCOORD";
		polygonLayout[2].SemanticIndex = 0;
		polygonLayout[2].Format = DXGI_FORMAT_R32G32_FLOAT;
		polygonLayout[2].InputSlot = 0;
		polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
		polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
		polygonLayout[2].InstanceDataStepRate = 0;

		// Get a count of the elements in the layout.
		unsigned int numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

		// Create the vertex input layout.
		HRESULT result = device->GetDevice()->CreateInputLayout(polygonLayout, 
											numElements, 
											mVertexShaderBuffer->GetBufferPointer(), 
											mVertexShaderBuffer->GetBufferSize(), 
											&mLayout);
		if(FAILED(result))
		{
			return false;
		}

		// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
		mVertexShaderBuffer->Release();
		mVertexShaderBuffer = NULL;

		mPixelShader->Release();
		mPixelShaderBuffer = NULL;

		// Create a texture sampler state description.
		D3D11_SAMPLER_DESC samplerDesc;
		samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
		samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
		samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
		samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
		samplerDesc.MipLODBias = 0.0f;
		samplerDesc.MaxAnisotropy = 1;
		samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
		samplerDesc.BorderColor[0] = 0;
		samplerDesc.BorderColor[1] = 0;
		samplerDesc.BorderColor[2] = 0;
		samplerDesc.BorderColor[3] = 0;
		samplerDesc.MinLOD = 0;
		samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

		// Create the texture sampler state.
		result = device->GetDevice()->CreateSamplerState(&samplerDesc, &mSampleState);
		if(FAILED(result))
		{
			return false;
		}
		return true;
	}
	return false;
}

void MultiTextureShader::DeinitializeShader()
{
	// Release the sampler state.
	if(mSampleState)
	{
		mSampleState->Release();
		mSampleState = 0;
	}

	// Release the matrix constant buffer.
	if(mMatrixBuffer)
	{
		mMatrixBuffer->Release();
		mMatrixBuffer = 0;
	}

	// Release the layout.
	if(mLayout)
	{
		mLayout->Release();
		mLayout = 0;
	}

	// Release the pixel shader.
	if(mPixelShader)
	{
		mPixelShader->Release();
		mPixelShader = 0;
	}

	// Release the vertex shader.
	if(mVertexShader)
	{
		mVertexShader->Release();
		mVertexShader = 0;
	}

	return;
}

bool MultiTextureShader::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, TextureArray* textures)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	MatrixBufferType* dataPtr;
	unsigned int bufferNumber;

	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Lock the constant buffer so it can be written to.
	Graphics::D3DDevice* device = GetPlatform()->GetDevice()->As<Graphics::D3DDevice>();

	result = device->GetDeviceContext()->Map(mMatrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->World = worldMatrix;
	dataPtr->View = viewMatrix;
	dataPtr->Projection = projectionMatrix;

	// Unlock the constant buffer.
	device->GetDeviceContext()->Unmap(mMatrixBuffer, 0);

	// Set the position of the constant buffer in the vertex shader.
	bufferNumber = 0;

	// Finanly set the constant buffer in the vertex shader with the updated values.
	device->GetDeviceContext()->VSSetConstantBuffers(bufferNumber, 1, &mMatrixBuffer);

	ID3D11ShaderResourceView** textarray = new ID3D11ShaderResourceView*[2];
	 for(int i = 0; i < textures->Size(); i++)
	 {
		textarray[i] = textures->operator[](i)->GetResource();
	 }

	// Set shader texture array resource in the pixel shader.
	device->GetDeviceContext()->PSSetShaderResources(0, textures->Size(), textarray);

	return true;
}